#include "FileHeader.h"
#include "CrcCalculator.h"
#include "CentralDirectory.h"
#include <stdio.h>
#include <string>

using namespace std;

FileHeader::FileHeader()
{
    this->headerSignature_ = FILE_HEADER_SIGNATURE;
    this->versionNedded_ = 0;
    this->flag_ = 0;
    this->compressionMethod_ = 0;
    this->lastModifiedTime_ = 0;
    this->lastModifiedDate_ = 0;
    this->crc_ = 0;
    this->compressedSize_ = 0;
    this->unCompressedSize_ = 0;
    this->fileNameLength_ = 0;
    this->extraFieldLength_ = 0;
    this->fileName_ = 0;
    this->extraField_ = 0;
    this->size_ = 0;
    this->fileData_ = 0;
    this->centralDirectory_ = 0;

}

//FileHeader::FileHeader(const char* path)
//{
//    this->headerSignature_ = FILE_HEADER_SIGNATURE;
//    this->versionNedded_ = 0;
//    this->flag_ = 0;
//    this->compressionMethod_ = 0;
//    this->lastModifiedTime_ = 0;
//    this->lastModifiedDate_ = 0;
//    this->crc_ = 0;
//    this->compressedSize_ = 0;
//    this->unCompressedSize_ = 0;
//    this->fileNameLength_ = strlen(path);
//    this->extraFieldLength_ = 0;
//    this->fileName_ = (char*) malloc(strlen(path));
//    memcpy(this->fileName_, path, this->fileNameLength_);
//    this->extraField_ = 0;
//    this->size_ = 0;
//    this->fileData_ = 0;
//    this->centralDirectory_ = 0;
//}

FileHeader::~FileHeader()
{
    if (extraField_)
        free(extraField_);
    if (fileName_)
        free(fileName_);
    if (fileData_)
        free(fileData_);
}

bool FileHeader::operator ==(const FileHeader& fileHeader)
{

    string fileName(this->fileName_, this->fileNameLength_);
    string otherFileName(fileHeader.fileName_, fileHeader.fileNameLength_);

    string extraField(this->extraField_);
    string otherExtraField(fileHeader.extraField_, extraField.size());

    if (this->compressedSize_ != fileHeader.compressedSize_)
        return false;

//    bool resp = fileName.compare(otherFileName) == 0;
    bool nameCompare = memcmp(this->fileName_, fileHeader.fileName_, this->fileNameLength_) == 0;
    return this->headerSignature_ == fileHeader.headerSignature_ &&
            this->versionNedded_ == fileHeader.versionNedded_ &&
            this->flag_ == fileHeader.flag_ &&
            this->compressionMethod_ == fileHeader.compressionMethod_ &&
            //            this->lastModifiedTime_ == fileHeader.lastModifiedTime_ &&
            //            this->lastModifiedDate_ == fileHeader.lastModifiedDate_ &&
            this->crc_ == fileHeader.crc_ &&
            this->compressedSize_ == fileHeader.compressedSize_ &&
            this->unCompressedSize_ == fileHeader.unCompressedSize_ &&
            this->fileNameLength_ == fileHeader.fileNameLength_ &&
            this->extraFieldLength_ == fileHeader.extraFieldLength_ &&
            this->size_ == fileHeader.size_ &&
            nameCompare &&
            memcmp(this->fileData_, fileHeader.fileData_, this->compressedSize_) == 0;
}

int FileHeader::calculateSize()
{
    return COMMON_FILE_HEADER_FIELDS_LENGHT + this->fileNameLength_ +
            this->extraFieldLength_ + this->compressedSize_;
}

float FileHeader::calculateCompressionPercentage()
{
    if (this->unCompressedSize_ == 0)
        return 0;
    float compressionPercentage = 100 - ((this->compressedSize_ * 100) / this->unCompressedSize_);
    return compressionPercentage;
}

void FileHeader::setContent(const char* content, unsigned int contentSize)
{
    releaseMemory(this->fileData_);
    this->fileData_ = (char*) malloc(contentSize + 1);
    strcpy(this->fileData_, content);
    this->fileData_[contentSize] = '\0';
}

void FileHeader::setExtraField(const char* extraField, unsigned int extraFieldLength)
{
    releaseMemory(this->extraField_);
    this->extraField_ = (char*) malloc(extraFieldLength + 1);
    strcpy(this->extraField_, extraField);
    this->extraField_[extraFieldLength] = '\0';
}

void FileHeader::setFileName(const char* name, unsigned int nameLength)
{
    releaseMemory(this->fileName_);
    this->fileName_ = (char*) malloc(nameLength + 1);
    strcpy(this->fileName_, name);
    this->fileName_[nameLength] = '\0';
}

void FileHeader::setCentralDirectory(CentralDirectory* centralDirectory)
{
    this->centralDirectory_ = centralDirectory;
}

void FileHeader::releaseMemory(char* ptr)
{
    if (ptr)
        free(ptr);
}

short FileHeader::parseTimeToShort(tm* time)
{
    short parsedTime = 0;

    int hours = time->tm_hour;
    parsedTime = parsedTime | hours;
    parsedTime <<= 6;
    int min = time->tm_min;
    parsedTime = parsedTime | min;
    parsedTime <<= 5;
    int sec = time->tm_sec / 2;
    parsedTime = parsedTime | sec;

    return parsedTime;
}

short FileHeader::parseDateToShort(tm* time)
{

    short parsedDate = 0;

    int year = time->tm_year - 80;
    parsedDate = parsedDate | year;
    parsedDate <<= 4;
    int month = time->tm_mon;
    parsedDate = parsedDate | month + 1;
    parsedDate <<= 5;
    int day = time->tm_mday;
    parsedDate = parsedDate | day;
    return parsedDate;
}

tm* FileHeader::parseTimeAndDate()
{
    struct tm* time = new tm();
    short lastModifiedTime = this->lastModifiedTime_;
    short lastModifiedDate = this->lastModifiedDate_;

    time->tm_hour = lastModifiedTime & 63488;
    time->tm_hour = time->tm_hour >> 11;
    time->tm_min = lastModifiedTime & 2016;
    time->tm_min = time->tm_min >> 5;
    time->tm_sec = lastModifiedTime & 31 * 2;
    time->tm_year = lastModifiedDate & 65024;
    time->tm_year = time->tm_year >> 9;
    time->tm_mon = lastModifiedDate & 480;
    time->tm_mon = time->tm_mon >> 5;
    time->tm_mday = lastModifiedDate & 31;

    return time;
}

string FileHeader::parseTimeToString()
{
    struct tm* time = parseTimeAndDate();
    time->tm_year += 80;
    time->tm_mon -= 1;
    time->tm_min -= 1;
    mktime(time);
    string tstr = asctime(time);
    tstr = tstr.substr(0, tstr.size() - 1);
    return tstr;
}

unsigned long FileHeader::calculateCRC()
{
    return calculateCRC32(this->fileData_, this->unCompressedSize_);
}

std::ostream& operator <<(std::ostream& out, FileHeader& fileHeader)
{
    out.write((char*) &fileHeader.headerSignature_, 4);
    out.write((char*) &fileHeader.versionNedded_, 2);
    out.write((char*) &fileHeader.flag_, 2);
    out.write((char*) &fileHeader.compressionMethod_, 2);
    out.write((char*) &fileHeader.lastModifiedTime_, 2);
    out.write((char*) &fileHeader.lastModifiedDate_, 2);
    out.write((char*) &fileHeader.crc_, 4);
    out.write((char*) &fileHeader.compressedSize_, 4);
    out.write((char*) &fileHeader.unCompressedSize_, 4);
    out.write((char*) &fileHeader.fileNameLength_, 2);
    out.write((char*) &fileHeader.extraFieldLength_, 2);
    out.write(fileHeader.fileName_, fileHeader.fileNameLength_);
    out.write(fileHeader.extraField_, fileHeader.extraFieldLength_);
    out.write(fileHeader.fileData_, fileHeader.compressedSize_);

    return out;
}